﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.Serialization.Formatters.Binary;
using GISCore;
using GISCore.Geometry;
using GISCore.Geometry.Partition;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;

namespace GISAzureCore
{
    public class CellEntity : TableServiceEntity
    {
        private const int LIMIT = (960 * 1024 * 3) / 4;
        private const int BUFFER_COUNT = 10;

        public byte[] CellData1 { get; set; }
        public byte[] CellData2 { get; set; }
        public byte[] CellData3 { get; set; }
        public byte[] CellData4 { get; set; }
        public byte[] CellData5 { get; set; }
        public byte[] CellData6 { get; set; }
        public byte[] CellData7 { get; set; }
        public byte[] CellData8 { get; set; }
        public byte[] CellData9 { get; set; }
        public byte[] CellData10 { get; set; }

        public CellEntity(string partitionKey, string rowKey)
            : base(partitionKey, rowKey) { }

        public CellEntity() 
            : base()
        {
            PartitionKey =  Guid.NewGuid().ToString();
            RowKey = string.Empty;
        }

        public CellEntity(ICellable cell, string rowKey)
            : this()
        {
            ConvertFromCell(cell);
            RowKey = rowKey;
        }

        #region Copyright (c) Lokad 2010-2011
        IEnumerable<byte[]> Data()
        {
            if (null != CellData1) yield return CellData1;
            if (null != CellData2) yield return CellData2;
            if (null != CellData3) yield return CellData3;
            if (null != CellData4) yield return CellData4;
            if (null != CellData5) yield return CellData5;
            if (null != CellData6) yield return CellData6;
            if (null != CellData7) yield return CellData7;
            if (null != CellData8) yield return CellData8;
            if (null != CellData9) yield return CellData9;
            if (null != CellData10) yield return CellData10;
        }

        public byte[] GetData()
        {
            var arrays = Data().ToArray();
            var buffer = new byte[arrays.Sum(a => a.Length)];

            var i = 0;
            foreach (var array in arrays)
            {
                Buffer.BlockCopy(array, 0, buffer, i, array.Length);
                i += array.Length;
            }

            return buffer;
        }

        public void SetData(byte[] data)
        {
            var setters = new Action<byte[]>[]
                {
                    b => CellData1 = b,
                    b => CellData2 = b,
                    b => CellData3 = b,
                    b => CellData4 = b,
                    b => CellData5 = b,
                    b => CellData6 = b,
                    b => CellData7 = b,
                    b => CellData8 = b,
                    b => CellData9 = b,
                    b => CellData10 = b
                };

            for (int i = 0; i < BUFFER_COUNT; i++)
            {
                if (i * 64 * 1024 < data.Length)
                {
                    var start = i * 64 * 1024;
                    var length = Math.Min(64 * 1024, data.Length - start);
                    var buffer = new byte[length];

                    Buffer.BlockCopy(data, start, buffer, 0, buffer.Length);
                    setters[i](buffer);
                }
                else
                {
                    setters[i](null); // discarding potential leftover
                }
            }
        }
        #endregion

        public void ConvertFromCell(ICellable cell)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                BinaryFormatter bf = new BinaryFormatter();
                bf.Serialize(ms, cell);
                SetData(ms.ToArray());                
            }
        }

        public ICellable ConvertToCell()
        {
            ICellable baseCell = null;

            byte[] data = GetData();
            using (MemoryStream ms = new MemoryStream(data))
            {
                BinaryFormatter bf = new BinaryFormatter();
                baseCell = bf.Deserialize(ms) as ICellable;
            }
            return baseCell;
        }

        public override string ToString()
        {
            return CellData1.ToString();
        }
    }

    public class CellDataContext : TableServiceContext
    {
        internal const string CellDataTableName = "Cells";

        public CellDataContext(string baseAddress, StorageCredentials credentials)
            : base(baseAddress, credentials) { this.MergeOption = System.Data.Services.Client.MergeOption.OverwriteChanges; }
        
        public IQueryable<CellEntity> Cells
        {
            get
            {
                return this.CreateQuery<CellEntity>(CellDataTableName);                
            }
        }

        public void AddCell(CellEntity cell)
        {
            this.AddObject(CellDataTableName, cell);
            this.SaveChanges();
        }

        public void DeleteCell(CellEntity cell)
        {
            this.DeleteObject(cell);
            this.SaveChanges();
        }

        public void UpdateCell(CellEntity cell)
        {
            this.UpdateObject(cell);
            this.SaveChanges();
        }

        public void EmptyTable()
        {
            foreach (var cell in this.Cells)
            {
                //this is a HORRIBLE practice, but I need a hack. Come  back later.
                try
                {
                    this.DeleteCell(cell);
                }
                catch (Exception) { }
            }
        }

        public static Expression<Func<CellEntity, bool>> CreatePredicate(IEnumerable<CloudQueueMessage> messages)
        {
            ParameterExpression pe = Expression.Parameter(typeof(CellEntity), "cell");
            Expression e = Expression.Property(pe, "RowKey");
            IEnumerable<Expression> equals = from msg in messages
                                             select Expression.Equal(e, Expression.Constant(msg.AsString));

            Expression filter = equals.Aggregate((l, r) => Expression.Or(l, r));

            return Expression.Lambda<Func<CellEntity, bool>>(filter, pe);
        }
    }
}
